﻿/*
 * ********************************************************************* *
  
Copyright HLRS, University Stuttgart

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
* ********************************************************************* *
*/

using System;
using System.IO;
using System.Xml;
using System.ServiceModel;
using System.ServiceModel.Channels;
using SirLibrary;

namespace GatewayLibrary
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ValidateMustUnderstand = false, 
        AddressFilterMode=AddressFilterMode.Any)]
    public class MessageRouterImpl : IGateway
    {
        // Reference to the corresponding SIR 
        private static SirClient m_sir = MessageRouterImpl.CreateSirServiceClient();

        private static SirClient CreateSirServiceClient()
        {
            string sirAddress = System.Configuration.ConfigurationManager.AppSettings["SIRAddress"];

            return new SirClient(new BasicHttpBinding(), new EndpointAddress(sirAddress));
        }

        private static Binding CreateBinding(ServiceType serviceType)
        {
            // in case of different invocation policies for internal and external invocations, adapt code here
            // e.g. just use secure communication for external message transmissions
            if (serviceType == ServiceType.InternalService)
            {
                WSHttpBinding serviceBinding = new WSHttpBinding(SecurityMode.None);
                return serviceBinding;
            }
            else
            {
                WSHttpBinding serviceBinding = new WSHttpBinding(SecurityMode.None);
                return serviceBinding;
            }
        }

        /// <summary>
        /// Creates a channel of a given type
        /// </summary>
        /// <param name="serviceType">Type of the channel to be created (internal and external)</param>
        /// <returns>IChannelFactory of given type</returns>
        private static IChannelFactory<IRequestChannel> CreateChannelFactory(ServiceType serviceType)
        {
            Binding serviceBinding = MessageRouterImpl.CreateBinding(serviceType);
            
            IChannelFactory<IRequestChannel> cf = serviceBinding.BuildChannelFactory<IRequestChannel>();
            cf.Open();

            return cf;
        }

        #region Parameter Handling

        /// <summary>
        /// Extracts a specific parameter within a SOAP message
        /// </summary>
        /// <param name="parameterName">The name of the parameter to be extracted</param>
        /// <param name="soapBody">A string representation of the corresponding SOAP body</param>
        /// <returns>The string representation of the corresponding parameter</returns>
        private static string extractParameter (string parameterName, string soapBody)
        {
            int startIndex = soapBody.IndexOf(parameterName) + parameterName.Length + 1;
            int stopIndex = soapBody.IndexOf("</", startIndex);
            string result = soapBody.Substring(startIndex, stopIndex - startIndex);
            return result;
        }

        /// <summary>
        /// Replaces a given parameter with a new value
        /// </summary>
        /// <param name="parameterName">The name of the parameter to be replaced</param>
        /// <param name="newParameterValue">The new value of the parameter to be set</param>
        /// <param name="soapBody">The string representation of the corresponding message SOAP body</param>
        /// <returns>The new value of the parameter</returns>
        private static string replaceParameter(string parameterName, string newParameterValue, string soapBody)
        {
            return soapBody.Replace(extractParameter(parameterName, soapBody), newParameterValue);
        }

        /// <summary>
        /// Modifies a given header in a given message
        /// </summary>
        /// <param name="parameterName">The parameter to be replaced</param>
        /// <param name="newParameterValue">The value to be set</param>
        /// <param name="message">A reference parameter of the message to be adapted</param>
        private void modifyMessageParameter(string parameterName, string newParameterValue, ref Message message)
        {
            // Create a buffered copy of the message in memory so we can read it AND take a another copy
            MessageBuffer buffer = message.CreateBufferedCopy(Int32.MaxValue);

            // Get a copy of the original message. This will be used to read and extract the body.
            Message msgCopy = buffer.CreateMessage();

            // Use the msgCopy to get an XML Dictionary reader to extract the body contents. Once this message has been read and consumed,
            // it can no longer be processed again (this is why we took the second copy above)
            System.Xml.XmlDictionaryReader xdr = msgCopy.GetReaderAtBodyContents();
            string bodyData = xdr.ReadOuterXml();

            string newSoapBody = replaceParameter(parameterName, newParameterValue, bodyData);

            StringReader modifiedSOAPBody = new StringReader(newSoapBody);

            XmlDictionaryReaderQuotas quotas = new XmlDictionaryReaderQuotas();
            xdr = XmlDictionaryReader.CreateDictionaryReader(XmlReader.Create(modifiedSOAPBody));

            Message replacedMessage = Message.CreateMessage(message.Version, null, xdr);
            replacedMessage.Headers.CopyHeadersFrom(message.Headers);
            replacedMessage.Properties.CopyProperties(message.Properties);

            message = replacedMessage;
        }

        #endregion

        #region IGateway Members

        /// <summary>
        /// Processes the incoming message
        /// </summary>
        /// <param name="message">The message to be processed</param>
        /// <returns></returns>
        Message IGateway.ProcessMessage(Message message)
        {
            OperationContext oc = OperationContext.Current;

            System.Console.Out.Write("\n\nreceived message:\n" + message.ToString() + "\n\n");

            string incomingTo = message.Headers.To.AbsoluteUri;

            EndpointInformation destination = m_sir.GetEndpoint(incomingTo);

            message.Headers.To = destination.GetEndpointAddressAsUri();

            IChannelFactory<IRequestChannel> channelFactory = MessageRouterImpl.CreateChannelFactory(destination.ServiceType);
            IRequestChannel destinationChannel = channelFactory.CreateChannel(destination.EndpointAddress.ToEndpointAddress());
            destinationChannel.Open();

            System.Console.Out.WriteLine("Forwarding message for {0} to {1}", 
                                            incomingTo, 
                                            destination.GetEndpointAddressAsUri().AbsoluteUri);

            Message reply = destinationChannel.Request(message);

            channelFactory.Close();

            System.Console.Out.Write("\n\nsending reply:\n" + reply.ToString() + "\n\n");

            return reply;
        }

        #endregion
    }
}